// This is unpublished source code. Michah Lerner 2006

package trieMatch.consoleApps;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;

import trieMatch.Constants;
import trieMatch.keywordMatcher.KeyMatch;
import trieMatch.keywordMatcher.ProcessQueries;
import trieMatch.keywordMatcher.Tiers;
import trieMatch.keywordMatcher.TrieMatcher;
import trieMatch.keywordMatcher.KeyMatch.EvalType;
import trieMatch.keywordMatcher.KeyMatch.MatcherActionDefinition;
import trieMatch.util.FileProcessor;
import trieMatch.util.TOD;
import trieMatch.util.data.IO;

/**
 * Driver invokes lexical trie match through java.util.concurrent.ExecutorService<br>
 * 1. Initialize trie matcher state from stable storage: kmPrimary = new KeyMatch(kmFilename)<br>
 * 2. Instantiate multi-threaded matcher: ListProcessorIF<String> ProcessQueries(numThread, outstream)<br>
 * 3. Connect queries to matcher: FileProcessor(ListProcessorIF)<br>
 * 4. Activate threads on queries: fileProcessor.process(filename)<br>
 * 
 * All threads share the same copy of the read-only trie index. Each thread has its own local store for parameters,
 * stack, explicit recursions, etc.
 * 
 * @author Michah.Lerner
 */
public class MultiQuery {

	static MatcherActionDefinition searchFlavor = MatcherActionDefinition.longest;
	public static KeyMatch kmPrimary;

	public static PrintStream outstream;
	public static final boolean _echo = false; // echo match inputs?
	public static final int keymatchFile = 0, testInputsfile = 1, outputFile = 2;
	public static final int _chunkSize = 100; // default chunksize
	public static final int _numThreads = 4; // default # threads
	static EvalType evalType;
	static List<String> filenames = new ArrayList<String>();
	static String aggregatorName = Constants.DEFAULT_AGGREGATOR_NAME;
	static String aggregatorParm = Constants.DEFAULT_AGGREGATOR_PARM;
	static String inputKMFilename;
	static String kmFilename = null, queryFilename = null, outputName = null;
	static boolean deleteOutputfile = false;
	static boolean echo = _echo;
	static boolean verify = false;

	static int numRep = 1;
	static int chunkSize;
	static int iterations;
	static int numThreads;

	public static void main(final String args[]) throws Exception {
		System.out.println(TOD.now() + ": EXECUTION BEGINS.");
		processArgs(args);
		showArgs();

		Tiers.mapFactoryInstance = MultiQuery.searchFlavor.getMapFactory();
		TrieMatcher.setAggregator(aggregatorName, aggregatorParm);

		System.out.println(TOD.now() + ": READ " + kmFilename);
		kmPrimary = new KeyMatch(kmFilename);
		final FileProcessor fileProcessor = new FileProcessor(new ProcessQueries(numThreads, outstream));

		showConfiguration(echo);

		System.out.println(TOD.now() + ": MATCHING BEGINS");
		fileProcessor.process(queryFilename.split(" +"));

		System.out.println(TOD.now() + ": EXECUTION COMPLETE");
	}

	public static void usage() {
		System.err.println("usage: java -jar file.jar [options] keymatchFilename testinputsFilename [outputFilename]");
		System.err.println("       options:  -all/-longest/-top    -echo/-noEcho      -deleteOutputFile   -numRep=#");
		System.err.println("                 -TH=#threads          -IT=#iterations    -CH=#queries/iteration/thread");
		System.err.println("                 -VERIFY               -pre[combinations [-per[mute also]]");
		System.exit(0);
	}

	static void processArgs(final String args[]) {
		echo = _echo;
		chunkSize = _chunkSize;
		numThreads = _numThreads;
		iterations = 0;
		evalType = KeyMatch.EvalType.eINP;
		for (int i = 0; i < args.length; i++) {
			if (args[i].equalsIgnoreCase("-h")) {
				usage();
			} else if (args[i].equalsIgnoreCase("-aggName")) {
				aggregatorName = args[++i];
			} else if (args[i].equalsIgnoreCase("-aggParm")) {
				aggregatorParm = args[++i];
			}
			if (args[i].equalsIgnoreCase("-all")) {
				searchFlavor = MatcherActionDefinition.all;
			} else if (args[i].equalsIgnoreCase("-longest")) {
				searchFlavor = MatcherActionDefinition.longest;
			} else if (args[i].equalsIgnoreCase("-top")) {
				searchFlavor = MatcherActionDefinition.top;
			} else if (args[i].equalsIgnoreCase("-exp")) {
				searchFlavor = MatcherActionDefinition.exp;
			} else if (args[i].startsWith("-TH=")) {
				numThreads = Integer.parseInt(args[i].substring(4));
				System.out.println("set # threads = " + numThreads);
			} else if (args[i].startsWith("-CH=")) {
				chunkSize = Integer.parseInt(args[i].substring(4));
				System.out.println("set # items per iteration per thread = " + chunkSize);
			} else if (args[i].startsWith("-IT=")) {
				iterations = Integer.parseInt(args[i].substring(4));
				System.out.println("set # iterations = " + iterations);
			} else if (args[i].startsWith("-numRep=")) {
				numRep = Integer.parseInt(args[i].substring(8));
				System.out.println("set # repetitions = " + numRep);
			} else if (args[i].equalsIgnoreCase("-echo")) {
				echo = true;
			} else if (args[i].equalsIgnoreCase("-noEcho")) {
				echo = false;
			} else if (args[i].equalsIgnoreCase("-verify")) {
				verify = true;
			} else if (args[i].equalsIgnoreCase("-deleteOutputFile")) {
				deleteOutputfile = true;
			} else {
				filenames.add(args[i]);
			}
		}
		filenames.add(null);
		if (filenames.size() < 2) {
			usage();
			System.exit(0);
		}
		System.out.printf("chunkSize=%d iterations=%d numRep=%d numThreads=%d searchFlavor=%s aggName=%s aggParm=%s%n", chunkSize,
				iterations, numRep, numThreads, searchFlavor, aggregatorName, aggregatorParm);
		kmFilename = filenames.get(keymatchFile);
		MultiQuery.inputKMFilename = kmFilename;
		queryFilename = filenames.get(testInputsfile);
		outputName = filenames.get(outputFile);
		IO.checkOutputfile(outputName, deleteOutputfile);
		try {
			outstream = (outputName != null)
					? safePrintStream(outputName)
					: null;
		} catch (final FileNotFoundException e) {
			e.printStackTrace();
		}
	}

	public static void showArgs() {
		showArgs("");
	}

	public static void showArgs(final String info) {
		System.out.println(TOD.now() + ": Search flavor   " + searchFlavor);
		System.out.println(TOD.now() + ": Aggregator name " + aggregatorName);
		System.out.println(TOD.now() + ": Aggregator parm " + aggregatorParm);
		try {
			System.out.println(TOD.now() + ": Keymatches      " + (new File(kmFilename)).getCanonicalPath());
			System.out.println(TOD.now() + ": Test input      " + (new File(queryFilename)).getCanonicalPath());
			String fname;
			if (outputName == null) {
				fname = "not printed";
			} else if (outputName.compareTo("-") == 0) {
				fname = "console";
			} else {
				fname = (new File(outputName)).getCanonicalPath();
			}
			System.out.println(TOD.now() + ": Results out     " + fname);

		} catch (final IOException e) {
			e.printStackTrace();
		}
		System.out.println(TOD.now() + ": numThread       " + numThreads);
		System.out.println(TOD.now() + ": chunkSize       " + chunkSize);
	}

	public static void showConfiguration(final boolean show) {
		if (show) {
			System.out.println(TOD.now() + ": #iterations " + iterations + " numThreads=" + numThreads + " chunksize=" + chunkSize);
			if (echo) {
				kmPrimary.tiers.printTiers(System.out);
			}
		}
	}

	public static void showStats(final long mtime, final int nQueries) {
		System.out.println("\n" + TOD.now() + ": Execution complete");
		showArgs();
		System.out.println(TOD.now() + ": Total queries   " + nQueries);
		System.out.println(TOD.now() + ": Chunks/thread   " + (int) Math.ceil((double) nQueries / (numThreads * chunkSize)));
		System.out.println(TOD.now() + ": Elapsed (ms)    " + mtime);
		System.out.println(TOD.now() + ": Queries / ms    " + (nQueries / (double) mtime));
		System.out.println(TOD.now() + ": Ms / query      " + ((double) mtime / ((double) nQueries)));
		System.out.println();
	}

	static boolean stdFilename(final String filename) {
		return filename == null || filename.length() == 0 || filename.equals("-");
	}

	static PrintStream fileFoundHandler(final String s) throws FileNotFoundException {
		throw new FileNotFoundException("File exists: " + s);
	}

	public static BufferedReader openInput(final String filename) throws Exception {
		return new BufferedReader((stdFilename(filename)
				? (new InputStreamReader(System.in))
				: (new FileReader(filename))));
	}

	public static PrintStream safePrintStream() throws FileNotFoundException {
		return safePrintStream(null);
	}

	public static PrintStream safePrintStream(final String filename) throws FileNotFoundException {
		return stdFilename(filename)
				? (new PrintStream(System.out))
				: ((!(new File(filename)).exists())
						? (new PrintStream(filename))
						: fileFoundHandler(filename));
	}

	public static void setNumRep(final int numRep) {
		MultiQuery.numRep = numRep;
	}

	public static int getNumRep() {
		return numRep;
	}

	public static MatcherActionDefinition getSearchFlavor() {
		return searchFlavor;
	}
}
